/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.commons.scripting;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;

import net.zarubsys.commons.scripting.internal.BlockStatement;
import net.zarubsys.commons.scripting.internal.FunctionStatement;
import net.zarubsys.commons.scripting.internal.TokenReader;
import net.zarubsys.commons.scripting.internal.ValueStatement;
import net.zarubsys.commons.scripting.utils.ScriptHelper;

/**
 * Based on scripting language from Media Solutions Slovakia, a.s.
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class ScriptingEngine {
    
	public static Script parseScript(Reader r, String fileName) throws ScriptingException, IOException {
        ParsedStatement st = parse(new TokenReader(r, fileName));
        return new Script(st);
	}
	
	public static Script parseScript(Reader r) throws ScriptingException, IOException {
        ParsedStatement st = parse(new TokenReader(r, null));
        return new Script(st);
	}

    /** parse input */
    private static ParsedStatement parse(TokenReader r) throws ScriptingException, IOException {
        List<ParsedStatement> stats = new LinkedList<ParsedStatement>();
        ParsedStatement stat;
        while ((stat = parseStatement(r)) != null) {
            stats.add(stat);
        }
        return new BlockStatement("<top>", r.getCurrentLocation(), stats);
    }
    
    /** parse statement */
    private static ParsedStatement parseStatement(TokenReader r) throws ScriptingException, IOException {
        while (true) {
            r.skipWhitespaces();
            int ic = r.readAhead();
            if (ic == -1)
                return null;
            if (Character.isLetter((char) ic) || (ic == '_')) {
                // function
                String name = r.readIdentifier();
                r.skipWhitespaces();
                if (r.read() != '(')
                    throw new ScriptingException("Missing '(' in function '" + name + "' call", r.getCurrentLocation());
                List<ParsedStatement> args = new LinkedList<ParsedStatement>();
                while (r.readAhead() != ')') {
                    if (r.readAhead() == -1)
                        throw new ScriptingException("Preliminary end of input", r.getCurrentLocation());
                    ParsedStatement s = parseStatement(r);
                    if (s == null)
                        throw new ScriptingException("Preliminary end of input", r.getCurrentLocation());
                    args.add(s);
                    r.skipWhitespaces();
                    if ((r.readAhead() != ',') && (r.readAhead() != ')'))
                        throw new ScriptingException("Missing ',' or ')' in function '" + name + "' call", r.getCurrentLocation());
                    if (r.readAhead() == ',')
                        r.read();
                }
                r.read();
                return new FunctionStatement(name, r.getCurrentLocation(), args);
            } else if ((ic == '"') || (ic == '\'')) {
                // string
                String str = r.readString();
                return new ValueStatement("'" + str + "'", r.getCurrentLocation(), str);
            } else if (ic == '#') {
                r.skipLine();
            } else if (ic == '/') {
                r.read();
                ic = r.readAhead();
                if (ic == '/')
                    r.skipLine();
                else if (ic == '*') {
                    r.read();
                    do {
                        ic = r.read();
                        if (ic == '*') {
                            ic = r.read();
                            if (ic == '/')
                                break;
                        }
                    } while (ic != -1);
                    if (ic == -1)
                        throw new ScriptingException("Unterminated '/*' comment", r.getCurrentLocation());
                } else
                    throw new ScriptingException("Parse error", r.getCurrentLocation());
            } else {
                // number
                String num = r.readNumber();
                Number number;
                if (num.endsWith("l") || num.endsWith("L")) {
                	try {
                		number = Long.valueOf(num.substring(0, num.length() - 1));
                	} catch (NumberFormatException e) {
                		throw new ScriptingException("'" + num + "' is not number or you added to much ending brackets", e, r.getCurrentLocation());
                	}
                } else if (num.endsWith("f") || num.endsWith("F")) {
                	try {
                		number = Float.valueOf(num.substring(0, num.length() - 1));
                	} catch (NumberFormatException e) {
                		throw new ScriptingException("'" + num + "' is not number or you added to much ending brackets", e, r.getCurrentLocation());
                	}
                } else if (num.endsWith("d") || num.endsWith("D")) {
                	try {
                		number = Double.valueOf(num.substring(0, num.length() - 1));
                	} catch (NumberFormatException e) {
                		throw new ScriptingException("'" + num + "' is not number or you added to much ending brackets", e, r.getCurrentLocation());
                	}
                } else {
                	try {
                		number = Integer.parseInt(num);
                	} catch (NumberFormatException e) {
                		throw new ScriptingException("'" + num + "' is not number or you added to much ending brackets", e, r.getCurrentLocation());
                	}
                }
                return new ValueStatement("'" + number + "'", r.getCurrentLocation(), number);
            }
        }
    }

	public static Script parseScript(File f) throws ScriptingException {
		return ScriptHelper.getInstance().parseScriptFromFile(f);
	}

}